﻿namespace Paw.Services
{
    using System;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Net;
    using System.Web;
    using System.Web.Security;
    using System.Xml;
    using Paw.Services.Infrastructure;
    using Paw.Services.MembershipWrappers;
    using Paw.Services.Serializers;
    using Microsoft.WindowsAzure;

    public class AzureTablesHandler : IHttpHandler
    {
        public AzureTablesHandler() :
            this(new RoleProviderWrapper(), new StorageRequestValidator(), new FormatSerializerFactory(), CloudStorageAccount.FromConfigurationSetting("DataConnectionString"))
        {
        }

        public AzureTablesHandler(RoleProvider roleProvider, IStorageRequestValidator requestValidator, IFormatSerializerFactory formatSerializerFactory, CloudStorageAccount cloudStorageAccount)
        {
            this.RoleProvider = roleProvider;
            this.RequestValidator = requestValidator;
            this.FormatSerializerFactory = formatSerializerFactory;
            this.CloudStorageAccount = cloudStorageAccount;
        }

        public bool IsReusable
        {
            get { return true; }
        }

        public RoleProvider RoleProvider { get; private set; }

        public IStorageRequestValidator RequestValidator { get; private set; }

        public IFormatSerializerFactory FormatSerializerFactory { get; private set; }

        public CloudStorageAccount CloudStorageAccount { get; private set; }

        public void ProcessRequest(HttpContext context)
        {
            if (this.RequestValidator.ValidateRequest(context))
            {
                var table = StorageRequestAnalyzer.GetRequestedTable(context.Request);
                var tableToCreate = string.Empty;

                if (StorageRequestAnalyzer.IsCreatingTable(context.Request, table))
                {
                    tableToCreate = StorageRequestAnalyzer.GetTableToCreate(context.Request);
                }

                var azureResponse = this.CopyRequestToAzureStorage(context);

                var serializer = this.FormatSerializerFactory.GetSerializer(context.Request.Headers, context.Request.QueryString);
                var originalBody = ExtractBodyFromResponse(azureResponse);
                var serializedContentType = string.Empty;
                var body = string.Empty;
                var isBodyEmpty = string.IsNullOrWhiteSpace(originalBody);

                if (!isBodyEmpty)
                {
                    var xmlDoc = new XmlDocument();
                    xmlDoc.LoadXml(originalBody);

                    body = serializer.SerializeReply(xmlDoc, out serializedContentType);
                }

                CopyAzureHeadersToResponse(serializedContentType, azureResponse, context.Response);
                CopyAzureBodyToResponse(body, context.Response, context.Request.Url, context.Request.FilePath);

                if (context.Response.StatusCode == 201)
                {
                    if (!string.IsNullOrEmpty(tableToCreate))
                    {
                        // add permissions to the current user to its new table
                        var userName = this.RequestValidator.GetUserName(context);

                        this.AddPermissionToNewTable(tableToCreate, userName);
                    }
                }

                context.Response.End();
            }
        }

        private static void CopyRequestHeaders(HttpRequest originalRequest, WebRequest finalRequest)
        {
            finalRequest.ContentType = originalRequest.ContentType;
            finalRequest.ContentLength = originalRequest.ContentLength;
            finalRequest.Headers.Add("DataServiceVersion", "1.0;NetFx");
            finalRequest.Headers.Add("MaxDataServiceVersion", "2.0;NetFx");

            var finalHttpWebRequest = finalRequest as HttpWebRequest;
            if (finalHttpWebRequest != null)
            {
                finalHttpWebRequest.Accept = HttpConstants.MimeApplicationAtomXml;
                finalHttpWebRequest.Connection = originalRequest["Connection"];
                finalHttpWebRequest.Referer = originalRequest.Headers["Referer"];
                finalHttpWebRequest.UserAgent = originalRequest.Headers["User-Agent"];
            }

            string[] excludeHeaders =
                {
                    "Connection",
                    "Accept",
                    "User-Agent",
                    "Host",
                    "DataServiceVersion",
                    "MaxDataServiceVersion",
                    "Authorization",
                    "AuthToken",
                    "x-ms-date",
                    "x-ms-version",
                    "Content-Length",
                    "Content-Type",
                    "Referer"
                };

            foreach (var header in originalRequest.Headers.AllKeys)
            {
                if (!excludeHeaders.Contains(header))
                {
                    finalRequest.Headers.Add(header, originalRequest.Headers[header]);
                }
            }
        }

        private static string ExtractBodyFromResponse(WebResponse azureResponse)
        {
            using (var stream = azureResponse.GetResponseStream())
            {
                return new StreamReader(stream).ReadToEnd();
            }
        }

        private static void CopyAzureHeadersToResponse(string contentType, WebResponse originalResponse, HttpResponse finalResponse)
        {
            finalResponse.ContentType = contentType;

            var httpWebResponse = originalResponse as HttpWebResponse;
            if (httpWebResponse != null)
            {
                finalResponse.StatusCode = (int)httpWebResponse.StatusCode;
                finalResponse.StatusDescription = httpWebResponse.StatusDescription;
            }

            string[] excludeHeaders = { "Content-Type", "Transfer-Encoding" };
            foreach (var header in originalResponse.Headers.AllKeys)
            {
                if (!excludeHeaders.Contains(header))
                {
                    finalResponse.Headers.Add(header, originalResponse.Headers[header]);
                }
            }
        }

        private static void CopyAzureBodyToResponse(string responseBody, HttpResponse response, Uri requestUrl, string requestPath)
        {
            var storageAccount = CloudStorageAccount.FromConfigurationSetting("DataConnectionString");
            var newResponseBody = responseBody.Replace(
                            storageAccount.TableEndpoint.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped),
                            string.Format(
                                CultureInfo.InvariantCulture,
                                "{0}{1}",
                                requestUrl.GetComponents(UriComponents.SchemeAndServer & ~UriComponents.Port, UriFormat.SafeUnescaped),
                                requestPath));

            response.Write(newResponseBody);
        }

        private static WebRequest CreateWebRequest(Uri newRequestUri)
        {
            return WebRequest.Create(newRequestUri);
        }

        private WebResponse CopyRequestToAzureStorage(HttpContext context)
        {
            var relativeUrl = (context.Request.QueryString.Count > 0)
                                ? string.Format(CultureInfo.InvariantCulture, "{0}?{1}", context.Request.PathInfo, context.Request.QueryString.ToString())
                                : context.Request.PathInfo;

            var devaccount = CloudStorageAccount.DevelopmentStorageAccount.Credentials.AccountName;

            if (CloudStorageAccount.Credentials.AccountName.Equals(devaccount, StringComparison.OrdinalIgnoreCase)
                && relativeUrl.StartsWith(string.Format(CultureInfo.InvariantCulture, "/{0}", devaccount), StringComparison.OrdinalIgnoreCase))
            {
                relativeUrl = relativeUrl.Remove(0, devaccount.Length + 1);
            }

            var newRequestUri = new Uri(string.Format(CultureInfo.InvariantCulture, "{0}{1}", this.CloudStorageAccount.TableEndpoint, relativeUrl), UriKind.Absolute);
            return this.GetAzureTablesResponse(newRequestUri, context.Request);
        }

        private WebResponse GetAzureTablesResponse(Uri newRequestUri, HttpRequest originalRequest)
        {
            var webRequest = CreateWebRequest(newRequestUri);
            webRequest.Method = originalRequest.HttpMethod;

            CopyRequestHeaders(originalRequest, webRequest);

            var httpWebRequest = webRequest as HttpWebRequest;
            if (httpWebRequest != null)
            {
                this.CloudStorageAccount.Credentials.SignRequestLite(httpWebRequest);
            }

            if (!originalRequest.HttpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
                !originalRequest.HttpMethod.Equals("DELETE", StringComparison.OrdinalIgnoreCase) &&
                originalRequest.ContentLength > 0)
            {
                var requestBody = string.Empty;
                var inputStream = originalRequest.InputStream;
                using (var reader = new StreamReader(inputStream))
                {
                    inputStream = null;
                    requestBody = reader.ReadToEnd().Replace(
                                    string.Format(
                                        CultureInfo.InvariantCulture,
                                        "{0}{1}",
                                        originalRequest.Url.GetComponents(UriComponents.SchemeAndServer & ~UriComponents.Port, UriFormat.SafeUnescaped),
                                        originalRequest.FilePath),
                                    newRequestUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped));
                }

                webRequest.ContentLength = requestBody.Length;
                var requestStream = webRequest.GetRequestStream();
                using (var writer = new StreamWriter(requestStream))
                {
                    requestStream = null;
                    writer.Write(requestBody);
                }
            }

            try
            {
                return webRequest.GetResponse();
            }
            catch (WebException webException)
            {
                return webException.Response;
            }
        }

        private void AddPermissionToNewTable(string tableName, string userName)
        {
            if (!string.IsNullOrEmpty(tableName))
            {
                var roleName = string.Format(CultureInfo.InvariantCulture, "{0}{1}", tableName, RoleConstants.TableRoleSuffix);

                if (!this.RoleProvider.RoleExists(roleName))
                {
                    this.RoleProvider.CreateRole(roleName);
                }

                if (!this.RoleProvider.IsUserInRole(userName, roleName))
                {
                    string[] userNames = { userName };
                    string[] roleNames = { roleName };

                    this.RoleProvider.AddUsersToRoles(userNames, roleNames);
                }
            }
        }
    }

}
